package collect

import (
	"context"
	"github.com/1141520944/go-library/library/log"
	"github.com/fsnotify/fsnotify"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// Collect 收集控制器
type Collect struct {
	indexPath  string //索引文件路径
	listenPath string //监听文件路径
	ctx        context.Context
	io         *fileIO     //文件操作
	listen     *fileListen //文件监听
	cron       *fileCorn   //定时任务
	tail       *fileTail   //tail
	log        *log.Logger
	indexes    []*Index          //索引文件
	mIndex     sync.Map          //索引文件 -索引
	Result     chan *SendLogInfo //读取的日志信息
	regular    bool              //正则
	listenDir  string            //正则第一个 *的目录
	midPath    string            // * * 之间的内容
	fileSuffix string            //监听文件的后缀 默认 .log
	tailInfo   chan string
}

// InitCollect 收集日志服务
func InitCollect(indexPath, indexName, ListenPath string, regular bool, log *log.Logger) (c *Collect, err error) {
	var (
		pre, mid string
	)
	if regular {
		// 分割路径
		pre, mid, err = extractPaths(c.listenPath)
		if err != nil {
			c.log.Errorw("method", "InitCollect extractPaths", "err", err.Error())
			return
		}
	}
	c = &Collect{
		indexPath:  filepath.Join(indexPath, indexName+IndexFile),
		listenPath: ListenPath,
		io:         nil,
		listen:     nil,
		cron:       nil,
		tail:       nil,
		log:        log,
		indexes:    make([]*Index, 0, 10),
		mIndex:     sync.Map{},
		Result:     make(chan *SendLogInfo, 10),
		regular:    regular,
		listenDir:  pre,
		midPath:    mid,
		fileSuffix: extractFileExtension(ListenPath),
		tailInfo:   make(chan string, 10),
	}
	go c.Start(context.Background())
	return
}
func (c *Collect) Start(ctx context.Context) (err error) {
	var (
		indexes []*Index
	)
	c.ctx = ctx
	// 文件IO
	c.io = NewFileIO(c.ctx, c.log)
	// 读取索引文件
	if indexes, err = c.io.IndexFileRead(c.indexPath); err != nil {
		c.log.Errorw("method", "InitCollect f.ReadIndexFile", "err", err.Error())
		return
	}
	for _, v := range indexes {
		c.log.Infow("read index files", v.Name)
		c.mIndex.Store(v.Name, len(c.indexes))
		c.indexes = append(c.indexes, v)
	}
	// 文件监听
	if c.listen, err = NewFileListen(c.ctx, c.log); err != nil {
		c.log.Errorw("method", "InitCollect NewFileListen", "err", err.Error())
		return
	}
	//tail
	c.tail = NewFileTail(c.log, c.ctx)
	if c.regular {
		var (
			tempList []string // 第一个*匹配的全部路径
		)
		// 监测 * 前的目录
		c.listen.Opt <- fOpt{
			data: c.listenDir,
			sign: Add,
		}
		// 监听所有匹配下的目录
		tempList, err = c.getDir(c.listenDir)
		if err != nil {
			c.log.Errorw("method", "InitCollect. c.getDir", "err", err.Error())
		}
		for _, v := range tempList {
			c.listen.Opt <- fOpt{
				data: v,
				sign: Add,
			}
		}
	} else {
		// 只监听listen目录
		c.listen.Opt <- fOpt{
			data: filepath.Dir(c.listenPath),
			sign: Add,
		}
	}
	go c.initListenDir()
	//监听服务 context
	go func() {
		for {
			select {
			case val := <-c.tail.Result:
				c.Result <- val
			case <-c.ctx.Done():
				c.log.Infow("collect Done .....", time.Now().Format(time.DateTime))
				break
			}
		}
	}()
	return
}

// getDir 获得当前层次下的目录
func (c *Collect) getDir(path string) (res []string, err error) {
	entries, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}
	for _, entry := range entries {
		if entry.IsDir() {
			res = append(res, filepath.Join(c.listenDir, entry.Name(), c.midPath))
		}
	}
	return res, nil
}

// initListenDir 判断不同文件事件下的操作
func (c *Collect) initListenDir() {
	for {
		select {
		case val := <-c.listen.Info: //获得文件监听的时间
			if val.event.Op&fsnotify.Create == fsnotify.Create {
				// 创建的文件夹在监听文件夹下 添加到监听中
				if filepath.Dir(val.event.Name) == c.listenDir && c.regular {
					path := filepath.Join(val.event.Name, c.midPath)
					// 验证目录
					err := EnsureDir(path)
					if err != nil {
						c.log.Errorw("method Collect.initListenDir c.EnsureDir", "err", err.Error())
						continue
					}
					c.listen.Opt <- fOpt{
						data: path,
						sign: Add,
					}
				} else {
					var (
						path = val.event.Name
					)
					if extractFileExtension(path) != c.fileSuffix {
						continue
					}
					if filepath.Base(path) == "track.log" {
						continue
					}
					//启动 tail
					c.tail.Create <- Create{
						Path:   path,
						Offset: 0,
					}
				}
			}
			if val.event.Op&fsnotify.Write == fsnotify.Write {
				// 注意：Write 事件也可能在文件被创建后立即触发，但通常表示文件内容发生了变化
				var (
					path = val.event.Name
				)
				// 判断是否是文件夹
				dir, err := checkPathIsDir(path)
				if err != nil {
					c.log.Errorw("method", "Collect.initListenDir checkPathIsDir", "err", err.Error())
				}
				if !dir {
					// 判断文件类型
					if c.fileSuffix != "" {
						// 如果文件类型不符合
						if extractFileExtension(path) != c.fileSuffix {
							continue
						}
					}
					//使用io
					{
						//判断是否
						_, ok := c.io.indexIO.Load(path)
						c.io.Create <- &Create{
							Path:   path,
							Offset: 0,
						}
						if !ok {
							time.Sleep(1 * time.Second)
						}
						res, err := c.io.LogFileRead(path)
						if err != nil {
							c.log.Errorw("method", "Collect.initListenDir c.io.LogFileRead", "err", err.Error())
						}
						for _, v := range res {
							c.Result <- v
						}
					}
				}
			}
		}
	}
}
func (c *Collect) TestCorn() {
	var (
		err error
	)
	// 定时任务
	// 1.定时更新索引文件
	updateIndex := func() {
		// println("start cron ...")
		// 获得实时索引信息
		info, b := c.io.GetIndexInfo()
		if b {
			if err := c.io.IndexFileWrite(c.indexPath, info); err != nil {
				c.log.Errorw("method", "collect  updateIndex.c.f.WriteIndexFIle", "err", err.Error())
				return
			}
			c.log.Infow("time", time.Now().Format(time.DateTime), "action", "update index file")
		}
	}
	// 2.定期删除 f.files 中的 os.file  每隔1天检测 文件路径为 当天和昨天的 其他不匹配的都删除
	delFileIO := func() {
		c.io.DelFileIO()
	}
	// s 秒 m 分钟 h 小时
	c.cron, err = NewFileCorn([]*Task{{
		TimeStr: "@every 1m", // 每隔1分钟更新配置文件
		opt:     updateIndex,
	}, {
		TimeStr: "@every 20h", //每隔20h更新 os.Files
		opt:     delFileIO,
	}}, c.ctx)
	if err != nil {
		c.log.Errorw("method", "collect  NewFileCorn", "err", err.Error())
		return
	}
}
